﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using NMapper.Mappings;
using NMapper.Utils;

namespace NMapper
{
    public class MapFactory<TDatabase, TDatabaseEntity, TOutput>
        where TOutput : class 
        where TDatabase : IDisposable
    {
        private readonly Mapper<TDatabase, TDatabaseEntity, TOutput> mapper;
        private readonly MappingContext<TDatabase> context;

        public MapFactory(Mapper<TDatabase, TDatabaseEntity, TOutput> mapper, MappingContext<TDatabase> context)
        {
            this.mapper = mapper;
            this.context = context;
        }

        public void Add(IMapping<TDatabase> mapping)
        {
            mapper.Add(mapping);
        }

        public SelectorsMapping<TDatabase, TDatabaseEntity, TOutput, TDbValue, TOutputValue> Selectors<TDbValue, TOutputValue>(Expression<Func<TDatabaseEntity, TDbValue>> dbSelector, Expression<Func<TOutput, TOutputValue>> outputSelector)
        {
            var mapping = new SelectorsMapping<TDatabase, TDatabaseEntity, TOutput, TDbValue, TOutputValue>((Expression<Func<MapContext<TDatabase>, TDatabaseEntity, TDbValue>>)dbSelector.PrependParameters(typeof(MapContext<TDatabase>)), outputSelector);
            mapper.Add(mapping);
            return mapping;
        }

        public PropertyMapping<TDatabase, TDatabaseEntity, TOutput, TDbValue, TOutputValue> Property<TDbValue, TOutputValue>(Expression<Func<TDatabaseEntity, TDbValue>> dbSelector, Expression<Func<TOutput, TOutputValue>> outputSelector)
        {
            var mapping = new PropertyMapping<TDatabase, TDatabaseEntity, TOutput, TDbValue, TOutputValue>((Expression<Func<MapContext<TDatabase>, TDatabaseEntity, TDbValue>>)dbSelector.PrependParameters(typeof(MapContext<TDatabase>)), outputSelector);
            mapper.Add(mapping);
            return mapping;
        }

        public PropertyMapping<TDatabase, TDatabaseEntity, TOutput, TDbValue, TOutputValue> Property<TDbValue, TOutputValue>(Expression<Func<MapContext<TDatabase>, TDatabaseEntity, TDbValue>> dbSelector, Expression<Func<TOutput, TOutputValue>> outputSelector)
        {
            var mapping = new PropertyMapping<TDatabase, TDatabaseEntity, TOutput, TDbValue, TOutputValue>(dbSelector, outputSelector);
            mapper.Add(mapping);
            return mapping;
        }

        public NormalizeMapping<TDatabase, TDatabaseEntity, TOutput, TOutputValue> Normalize<TOutputValue>(Expression<Func<TOutput, TOutputValue>> outputSelector, Mapper<TDatabase, TDatabaseEntity, TOutputValue> mapper)
            where TOutputValue : class
        {
            var mapping = new NormalizeMapping<TDatabase, TDatabaseEntity, TOutput, TOutputValue>(outputSelector, mapper);
            this.mapper.Add(mapping);
            return mapping;
        }

/*
        public DenormalizeMapping<TDatabase, TDatabaseEntity, TOutput, TChildDbEntity> Denormalize<TChildDbEntity>(
            Expression<Func<TDatabaseEntity, TChildDbEntity>> dbSelector, 
            IMapper<TDatabase, TChildDbEntity, TOutput> mapper)
        {
            var mapping = new DenormalizeMapping<TDatabase, TDatabaseEntity, TOutput, TChildDbEntity>(dbSelector, mapper);
            this.mapper.Add(mapping);
            return mapping;
        }
*/

        public TransientMapping<TDatabase, TDatabaseEntity, TDbValue> Transient<TDbValue>(Expression<Func<MapContext<TDatabase>, TDatabaseEntity, TDbValue>> dbSelector)
        {
            var mapping = new TransientMapping<TDatabase, TDatabaseEntity, TDbValue>(dbSelector);
            mapper.Add(mapping);
            return mapping;
        }

        public TransientMapping<TDatabase, TDatabaseEntity, TDbValue> Transient<TDbValue>(Expression<Func<TDatabaseEntity, TDbValue>> dbSelector)
        {
            var mapping = new TransientMapping<TDatabase, TDatabaseEntity, TDbValue>((Expression<Func<MapContext<TDatabase>, TDatabaseEntity, TDbValue>>)dbSelector.PrependParameters(typeof(MapContext<TDatabase>)));
            mapper.Add(mapping);
            return mapping;
        }

        public DiscriminatorMapping<TDatabase, TDatabaseEntity, TOutput, TDbValue> Discriminator<TDbValue>(Expression<Func<MapContext<TDatabase>, TDatabaseEntity, TDbValue>> dbSelector, Func<TOutput, TDbValue> outputSelector)
        {
            var mapping = new DiscriminatorMapping<TDatabase, TDatabaseEntity, TOutput, TDbValue>(dbSelector, outputSelector);
            mapper.Add(mapping);
            return mapping;
        }

        public DiscriminatorMapping<TDatabase, TDatabaseEntity, TOutput, TDbValue> Discriminator<TDbValue>(Expression<Func<TDatabaseEntity, TDbValue>> dbSelector, Func<TOutput, TDbValue> outputSelector)
        {
            var mapping = new DiscriminatorMapping<TDatabase, TDatabaseEntity, TOutput, TDbValue>((Expression<Func<MapContext<TDatabase>, TDatabaseEntity, TDbValue>>)dbSelector.PrependParameters(typeof(MapContext<TDatabase>)), outputSelector);
            mapper.Add(mapping);
            return mapping;
        }

        public SubFetchMapping<TDatabase, TDatabaseEntity, TChildDbEntity, TOutput, TChildOutput, TPrimaryKey, TOutputValue> Fetch<TChildDbEntity, TChildOutput, TPrimaryKey, TOutputValue>(
            IDbValueMapping<TDatabase, TDatabaseEntity, TPrimaryKey> primaryKey,
            IDbValueMapping<TDatabase, TChildDbEntity, TPrimaryKey> foreignKey,
            Func<TDatabase, IEnumerable<TPrimaryKey>, IQueryable<TChildDbEntity>> fetcher,
            Mapper<TDatabase, TChildDbEntity, TChildOutput> childMapper,
            Expression<Func<TOutput, TOutputValue>> outputSelector
        )
            where TOutputValue : class
            where TChildOutput : class
        {
            var mapping = new SubFetchMapping<TDatabase, TDatabaseEntity, TChildDbEntity, TOutput, TChildOutput, TPrimaryKey, TOutputValue>(primaryKey, foreignKey, fetcher, childMapper, outputSelector);
            mapper.Add(mapping);
            return mapping;
        }

        public SequenceMapping<TDatabase, TDatabaseEntity, TDbValue, TChildDbEntity, TOutput, TChildOutput, TOutputValue> Sequence<TDbValue, TChildDbEntity, TChildOutput, TOutputValue>(
            Expression<Func<MapContext<TDatabase>, TDatabaseEntity, TDbValue>> dbSelector, 
            Expression<Func<TOutput, TOutputValue>> outputSelector,
            Mapper<TDatabase, TChildDbEntity, TChildOutput> mapper
        ) 
            where TDbValue : IEnumerable<TChildDbEntity>
            where TChildDbEntity : class
            where TOutputValue : class
            where TChildOutput : class
        {
            var mapping = new SequenceMapping<TDatabase, TDatabaseEntity, TDbValue, TChildDbEntity, TOutput, TChildOutput, TOutputValue>(dbSelector, outputSelector, mapper);
            this.mapper.Add(mapping);
            return mapping;
        }

        public SequenceMapping<TDatabase, TDatabaseEntity, TDbValue, TChildDbEntity, TOutput, TChildOutput, TOutputValue> Sequence<TDbValue, TChildDbEntity, TChildOutput, TOutputValue>(
            Expression<Func<TDatabaseEntity, TDbValue>> dbSelector, 
            Expression<Func<TOutput, TOutputValue>> outputSelector,
            Mapper<TDatabase, TChildDbEntity, TChildOutput> mapper
        ) 
            where TDbValue : IEnumerable<TChildDbEntity>
            where TChildDbEntity : class
            where TOutputValue : class
            where TChildOutput : class
        {
            var mapping = new SequenceMapping<TDatabase, TDatabaseEntity, TDbValue, TChildDbEntity, TOutput, TChildOutput, TOutputValue>((Expression<Func<MapContext<TDatabase>, TDatabaseEntity, TDbValue>>)dbSelector.PrependParameters(typeof(MapContext<TDatabase>)), outputSelector, mapper);
            this.mapper.Add(mapping);
            return mapping;
        }

        public EntityMapping<TDatabase, TDatabaseEntity, TChildDbEntity, TOutput, TChildOutput> Entity<TChildDbEntity, TChildOutput>(
            Expression<Func<MapContext<TDatabase>, TDatabaseEntity, TChildDbEntity>> dbSelector, 
            Expression<Func<TOutput, TChildOutput>> outputSelector,
            Mapper<TDatabase, TChildDbEntity, TChildOutput> mapper
        ) 
            where TChildDbEntity : class
            where TChildOutput : class
        {
            var mapping = new EntityMapping<TDatabase, TDatabaseEntity, TChildDbEntity, TOutput, TChildOutput>(dbSelector, outputSelector, mapper);
            this.mapper.Add(mapping);
            return mapping;
        }

        public EntityMapping<TDatabase, TDatabaseEntity, TChildDbEntity, TOutput, TChildOutput> Entity<TChildDbEntity, TChildOutput>(
            Expression<Func<TDatabaseEntity, TChildDbEntity>> dbSelector, 
            Expression<Func<TOutput, TChildOutput>> outputSelector,
            Mapper<TDatabase, TChildDbEntity, TChildOutput> mapper
        ) 
            where TChildDbEntity : class
            where TChildOutput : class
        {
            var mapping = new EntityMapping<TDatabase, TDatabaseEntity, TChildDbEntity, TOutput, TChildOutput>((Expression<Func<MapContext<TDatabase>, TDatabaseEntity, TChildDbEntity>>)dbSelector.PrependParameters(typeof(MapContext<TDatabase>)), outputSelector, mapper);
            this.mapper.Add(mapping);
            return mapping;
        }

        public EntityRefMapping<TDatabase, TDatabaseEntity, TChildDbId, TChildDbEntity, TOutput, TChildOutput> EntityRef<TChildDbId, TChildDbEntity, TChildOutput>(
            Expression<Func<MapContext<TDatabase>, TDatabaseEntity, TChildDbId>> dbIdSelector, 
            Expression<Func<MapContext<TDatabase>, TDatabaseEntity, TChildDbEntity>> dbSelector, 
            Expression<Func<TOutput, TChildOutput>> outputSelector,
            Mapper<TDatabase, TChildDbEntity, TChildOutput> mapper
        ) 
            where TChildDbEntity : class
            where TChildOutput : class
        {
            var mapping = new EntityRefMapping<TDatabase, TDatabaseEntity, TChildDbId, TChildDbEntity, TOutput, TChildOutput>(dbIdSelector, dbSelector, outputSelector, mapper);
            this.mapper.Add(mapping);
            return mapping;
        }

        public EntityRefMapping<TDatabase, TDatabaseEntity, TChildDbId, TChildDbEntity, TOutput, TChildOutput> EntityRef<TChildDbId, TChildDbEntity, TChildOutput>(
            Expression<Func<TDatabaseEntity, TChildDbId>> dbIdSelector, 
            Expression<Func<TDatabaseEntity, TChildDbEntity>> dbSelector, 
            Expression<Func<TOutput, TChildOutput>> outputSelector,
            Mapper<TDatabase, TChildDbEntity, TChildOutput> mapper
        ) 
            where TChildDbEntity : class
            where TChildOutput : class
        {
            var mapping = new EntityRefMapping<TDatabase, TDatabaseEntity, TChildDbId, TChildDbEntity, TOutput, TChildOutput>(
                (Expression<Func<MapContext<TDatabase>, TDatabaseEntity, TChildDbId>>)dbIdSelector.PrependParameters(typeof(MapContext<TDatabase>)), 
                (Expression<Func<MapContext<TDatabase>, TDatabaseEntity, TChildDbEntity>>)dbSelector.PrependParameters(typeof(MapContext<TDatabase>)), 
                outputSelector, 
                mapper);
            this.mapper.Add(mapping);
            return mapping;
        }

        public MappingContext<TDatabase> Context
        {
            get { return context; }
        }
    }
}